#include "RpcProvider.hpp"
#include "RpcReadConfFile.hpp"
#include "transmissionprotocol.pb.h"
#include <iostream>
#include <functional>

std::string ProjectName = "RPC Network Communication Framework";

void RpcProvider::OnConnection(const muduo::net::TcpConnectionPtr &conn)
{
    if (!conn->connected())
    {
        // 放入log日志中
        conn->shutdown();
    }
    return;
}

//这个回调函数执行的实际就是在CallMethod之后，无论CallMethod是否正常退出正常执行，ResponseSend都会被调用
void RpcProvider::ResponseSend(const muduo::net::TcpConnectionPtr &conn, ::google::protobuf::Message *responce)
{
    // responce已经被CallMethod填充，现在序列化准备发送
    std::string send_str;
    if (!responce->SerializePartialToString(&send_str))
    {
        std::cout << "waring responce SerializePartialToString before net send" << std::endl;
        conn->shutdown();
    }

    conn->send(send_str);
}

// 只有咱们注册的回调函数OnMessage才能很好的知道有人发起了业务请求

// 因为的字节流传输，所以我们得解决粘包问题，在读取的时候必须约定好协议
// 在json中我们约定了键值对作为传输协议，那么在protobuf中没有键值对，在传输的时候直接
//(NameStr_size)ServerName methodName (ParameterStr_size)parameter
void RpcProvider::OnMessage(const muduo::net::TcpConnectionPtr &conn,
                            muduo::net::Buffer *buffer,
                            muduo::Timestamp timeStamp)
{
    std::string recv_buf = buffer->retrieveAllAsString();

    // 直接截取前四个字节，copy的好处就在于他的参数是一个输出型参数
    uint32_t headerSize = recv_buf.copy((char *)&headerSize, 4, 0);
    // 注意了本headerSize是包含了后面的参数的长度的
    std::string Names = recv_buf.substr(4, headerSize);

    TransmissionProtocol::Transmission transmission;
    if (!transmission.ParseFromString(Names))
    {
        std::cout << "transmission ParseFromString fail" << std::endl;
        exit(EXIT_FAILURE);
    }

    std::string serverName = transmission.servername();
    std::string methodName = transmission.methodname();
    uint32_t argsSize = transmission.args_size();

    std::string args = recv_buf.substr(4 + headerSize, argsSize);

    std::cout << "serverName :" << " " << serverName << "" << std::endl;
    std::cout << "methodName :" << " " << methodName << "" << std::endl;
    std::cout << "args       :" << " " << args << "" << std::endl;

    // 在_Map中查找是否有对应的服务和对应的服务方法
    auto itService = _serviceInfoMap.find(serverName);
    if (itService == _serviceInfoMap.end())
    {
        std::cout << serverName << " " << "is no exist" << std::endl;
        exit(EXIT_FAILURE);
    }

    ServiceInfo SI = itService->second;
    // 拿到*service
    ::google::protobuf::Service *service = SI.pService;

    auto itMethod = SI._serviceMethodMap.find(methodName);
    if (itMethod == SI._serviceMethodMap.end())
    {
        std::cout << methodName << " " << "is no exist" << std::endl;
        exit(EXIT_FAILURE);
    }

    // 拿到methodDesc
    const ::google::protobuf::MethodDescriptor *methodDesc = itMethod->second;

    // 调用本模块上的login业务，然后填充response返回给客户端
    //  void Login(::google::protobuf::RpcController *controller,
    //         const ::RpcProto::LoginRequest *request,
    //         ::RpcProto::LoginResponse *response,
    //         ::google::protobuf::Closure *done);

    // 获取requset对象信息，现在可以从里面解析出参数信息了
    // 通过GetRequestPrototypeh获取requset对象
    ::google::protobuf::Message *requset = service->GetRequestPrototype(methodDesc).New();
    if (!requset->ParseFromString(args))
    {
        std::cout << "args:" << args << " " << "反序列化解析服务对象方法的参数失败" << std::endl;
    }
    // 通过GetRequestPrototypeh创建requset对象，在调用的时候就可以创建requset的对象了
    ::google::protobuf::Message *responce = service->GetResponsePrototype(methodDesc).New();

    // service描述服务类，method描述方法，done是回调函数需要重写

    //   virtual void CallMethod(const MethodDescriptor* method,
    //                       RpcController* controller,
    //                       const Message* request,
    //                       Message* response,
    //                       Closure* done) = 0;

    //直接显示指定我们要调用的类对象，和回调函数的两个参数类型
    ::google::protobuf::Closure *done = ::google::protobuf::NewCallback<RpcProvider,
                                                                       const muduo::net::TcpConnectionPtr&,
                                                                        ::google::protobuf::Message*>
                                                                        (this, &RpcProvider::ResponseSend,conn,responce);

    service->CallMethod(methodDesc, nullptr, requset, responce, done);
}

bool RpcProvider::PrintRegisterService()
{
    bool ret = false;
    for (auto &service : _serviceInfoMap)
    {
        ServiceInfo SI = service.second;
        std::cout << "Service:" << service.first << " " << "Include Method:";
        for (auto &serviceMethod : SI._serviceMethodMap)
        {
            std::cout << serviceMethod.second->name() << " ";
        }
        std::cout << std::endl;
        if (ret == false)
            ret = true;
    }

    return ret;
}

// 如何实现NotifyService？, 首先我们不能确定用户什么时候调用执行已经注册了的不同模块的业务，不知道何时处理这个业务
// 所以这个业务交给回调函数，注册在OnMessage中，当有事件请求，就会调用OnMessage
// 所以NotiFfyService应该用map保留调用关系，方便OnMesssage可以调用到对应的业务
bool RpcProvider::NotifyService(::google::protobuf::Service *serivce)
{
    // 本api的作用就是填充“业务注册表”

    ServiceInfo serviceInfo;
    serviceInfo.pService = serivce;

    static const ::google::protobuf::ServiceDescriptor *pServiceDesc = serivce->GetDescriptor();
    int methodCount = pServiceDesc->method_count();
    const std::string serviceName = pServiceDesc->name();

    for (int i = 0; i < methodCount; ++i)
    {
        const ::google::protobuf::MethodDescriptor *pMethodDesc = pServiceDesc->method(i);
        const std::string methodName = pMethodDesc->name();

        serviceInfo._serviceMethodMap.insert(std::make_pair(methodName, pMethodDesc));
    }
    _serviceInfoMap.insert(std::make_pair(serviceName, serviceInfo));

    return PrintRegisterService();
}

// 即启动muduo网络库,初始化moduo网络库,写好对应的回调函数
void RpcProvider::start()
{
    // 填充TcpServer以启动服务

    std::string serverIp = RpcReadConfFile::GetRpcReadConfFile().GetConfMapMessage("serverip");
    std::string stringServerPort = RpcReadConfFile::GetRpcReadConfFile().GetConfMapMessage("serverport");

    std::cout << "IP:" << serverIp << " " << "port:" << stringServerPort << std::endl;

    if (serverIp == "" || stringServerPort == "")
        std::cout << "error: not found ConfMessage of Server" << std::endl;

    uint16_t port = atoi(stringServerPort.c_str());

    muduo::net::InetAddress listenAddr(serverIp, port);
    muduo::net::TcpServer tcpServer(&_eventloop, listenAddr, ProjectName);

    // 注册回调函数，设置线程数

    // 由于这里我们要设置一个类内的回调函数，类内的函数调用是需要对象的，只传入函数名是不行的，我们之间使用bind绑定器
    tcpServer.setConnectionCallback(std::bind(&RpcProvider::OnConnection, this, std::placeholders::_1));
    tcpServer.setMessageCallback(std::bind(&RpcProvider::OnMessage, this,
                                           std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

    tcpServer.setThreadNum(4);

    // 开始监听并启动loop循环
    tcpServer.start();
    _eventloop.loop();

    // 注意 ：以上代码更不就不需要什么什么异常判断路径，因为muduo网络库里很好的实现的对应的log日志
}